﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ZHello.OS
{
    /// <summary>
    /// 应用程序更新单元
    /// </summary>
    public class AppUpdateUtil
    {
        /// <summary>
        /// 更新文件下载的目录
        /// </summary>
        private static string BaseUpdateDir = ".\\update";

        private static string URL_GetVer = "http://192.168.19.216:20021//{0}";
        private static string URL_GetVerURL = "http://192.168.19.216:20021//{0}";

        public static void RunUpdate()
        {
            string version;
            if (!CheckUpdaVersion(out version))
            {
                MessageBox.Show("当前软件已是最新版本.", "提示");
                return;
            }
            var zipFile = "";
            if (!DownLoadPkg(version, out zipFile))
            {
                MessageBox.Show("下载更新失败，请手动更新.", "提示");
                return;
            }
            if (!Unzip(zipFile))
            {
                MessageBox.Show("解压失败，请手动更新.", "提示");
                return;
            }
            var newVersionDir = string.Format(BaseUpdateDir + "\\{0}", Path.GetFileNameWithoutExtension(zipFile));
            var ignoreDirs = new string[] { "log", "update" };
            var ignoreFiles = new string[] { "J010.GroundStation.db" };
            //备份当前程序文件，然后从解压目录移动程序文件到当前进程目录下
            DoUpdate(newVersionDir, ignoreDirs, ignoreFiles);
            //TODO:下次启动前检测上次升级的备份程序文件进行清除
            if (MessageBox.Show("更新完成，重启程序.", "提示", MessageBoxButtons.OK) == DialogResult.OK)
                Application.Restart();
        }

        #region 检测更新

        /// <summary>
        /// 从更新服务器请求最新的软件版本号
        /// </summary>
        /// <returns></returns>
        private static bool CheckUpdaVersion(out string version)
        {
            version = "1.0.0.1001";
            try
            {
                var url = string.Format(URL_GetVer, Application.ProductName);
                var request = WebRequest.Create(url) as HttpWebRequest;
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                var stream = response.GetResponseStream();
                var reader = new StreamReader(stream);
                var txt = reader.ReadToEnd();
                //TODO:解析结果,获取新版本号，验证是否需要更新,若需要更新则输出需要更新的版本号
                return true;
            }
            catch (Exception ex)
            {
            }
            return true;
        }

        #endregion 检测更新

        #region 下载与解压

        /// <summary>
        /// 根据版本号，从更新服务器下载软件更新的压缩文件
        /// </summary>
        /// <param name="version"></param>
        /// <param name="zipFile"></param>
        /// <returns></returns>
        private static bool DownLoadPkg(string version, out string zipFile)
        {
            try
            {
                var durl = GetNewVersionDownloadURL(version);
                zipFile = string.Format(BaseUpdateDir + "\\{0}.zip", version);
                HttpDownloadFile(durl, zipFile);
                return true;
            }
            catch (Exception ex)
            {
            }
            zipFile = "";
            return false;
        }

        /// <summary>
        /// 根据版本号从文件服务器，文件获取下载地址
        /// </summary>
        /// <param name="version"></param>
        /// <returns></returns>
        private static string GetNewVersionDownloadURL(string version)
        {
            return "http://192.168.19.216:20021/Other/ShareWeb.zip";

            // TODO:依据要更新的版本好，从更新服务器获取该版本的程序下载地址
            var url = string.Format(URL_GetVerURL, version);
            var request = WebRequest.Create(url) as HttpWebRequest;
            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            var stream = response.GetResponseStream();
            var reader = new StreamReader(stream);
            var txt = reader.ReadToEnd();
            //TODO:解析结果
            return txt;
        }

        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="url"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        private static string HttpDownloadFile(string url, string path)
        {
            var dir = Path.GetDirectoryName(path);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            // 设置参数
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            //发送请求并获取相应回应数据
            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            //直到request.GetResponse()程序才开始向目标网页发送Post请求
            Stream responseStream = response.GetResponseStream();
            //创建本地文件写入流
            Stream stream = new FileStream(path, FileMode.Create);
            byte[] bArr = new byte[1024];
            int size = responseStream.Read(bArr, 0, (int)bArr.Length);
            while (size > 0)
            {
                stream.Write(bArr, 0, size);
                size = responseStream.Read(bArr, 0, (int)bArr.Length);
            }
            stream.Close();
            responseStream.Close();
            return path;
        }

        /// <summary>
        /// 解压文件
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        private static bool Unzip(string file)
        {
            if (!File.Exists(file))
                return false;
            var dir = Path.GetDirectoryName(file);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            //TODO:引入解压缩类库将压缩文件解压到压缩文件同名的目录下
            return true;
        }

        #endregion 下载与解压

        #region 覆盖安装

        /// <summary>
        /// 递归删除空文件夹
        /// </summary>
        /// <param name="dir"></param>
        public static void ClearEmptyDir(string dir)
        {
            if (!Directory.Exists(dir))
                return;
            var dinfo = new DirectoryInfo(dir);
            foreach (var item in dinfo.GetDirectories())
            {
                ClearEmptyDir(item.FullName);
            }
            if (dinfo.GetFileSystemInfos().Length == 0)
                Directory.Delete(dir);
        }

        /// <summary>
        /// 递归查找指定目录下是所有文件（不含文件夹）
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="exptDirs"></param>
        /// <returns></returns>
        public static List<FileInfo> GetAllFiles(string dir, string[] exptDirs)
        {
            if (string.IsNullOrEmpty(dir))
                return new List<FileInfo>();
            if (!Directory.Exists(dir))
                return new List<FileInfo>();
            var dinfo = new DirectoryInfo(dir);
            var ret = new List<FileInfo>();
            foreach (var item in dinfo.GetDirectories())
            {
                if (exptDirs.Contains(item.Name))
                    continue;
                ret.AddRange(GetAllFiles(item.FullName, exptDirs));
            }
            foreach (var item in dinfo.GetFiles())
            {
                ret.Add(item);
            }
            return ret;
        }

        /// <summary>
        /// 计算更新文件的具体信息
        /// </summary>
        /// <param name="rUpdateDir"></param>
        /// <param name="ignoreDirs"></param>
        /// <param name="ignoreFiles"></param>
        private static void DoUpdate(string rUpdateDir, string[] ignoreDirs, string[] ignoreFiles = null)
        {
            var ass = Assembly.GetExecutingAssembly();
            var appDir = Path.GetDirectoryName(ass.Location);
            var oldVer = BaseUpdateDir + "\\old_" + ass.GetName().Version.ToString(4);
            List<string> oldFiles = new List<string>();
            List<string> newFiles = new List<string>();
            var fss1 = GetAllFiles(appDir, ignoreDirs);
            foreach (var item in fss1)
            {
                if (ignoreFiles != null && ignoreFiles.Contains(item.Name))
                    continue;
                oldFiles.Add(item.FullName.Remove(0, appDir.Length + 1));
            }
            var appDir2 = appDir + rUpdateDir;
            fss1 = GetAllFiles(appDir2, ignoreDirs);
            foreach (var item in fss1)
            {
                if (ignoreFiles != null && ignoreFiles.Contains(item.Name))
                    continue;
                newFiles.Add(item.FullName.Remove(0, appDir2.Length));
            }
            Update(appDir, oldVer, appDir2, oldFiles.ToArray(), newFiles.ToArray());
        }

        /// <summary>
        /// 更新失败时，可能需要恢复更新前的版本
        /// </summary>
        private static void Recovery(string rUpdateDir, string[] ignoreDirs, string[] ignoreFiles = null)
        {
        }

        /// <summary>
        /// 真正执行程序文件更新
        /// </summary>
        /// <param name="appDir"></param>
        /// <param name="oldVersion"></param>
        /// <param name="updateDir"></param>
        /// <param name="oldFiles"></param>
        /// <param name="newFiles"></param>
        private static void Update(string appDir, string oldVersion, string updateDir, string[] oldFiles, string[] newFiles)
        {
            var tempDir = Path.Combine(appDir, oldVersion);
            if (!Directory.Exists(tempDir))
            {
                Directory.CreateDirectory(tempDir);
            }
            var list = new List<string>();
            //Move Old Files
            for (int i = 0; i < oldFiles.Length; i++)
            {
                var f1 = Path.Combine(appDir, oldFiles[i]);
                var f2 = Path.Combine(tempDir, oldFiles[i]);
                if (File.Exists(f1))
                {
                    var dir = Path.GetDirectoryName(f2);
                    if (!Directory.Exists(dir))
                        Directory.CreateDirectory(dir);
                    if (!MoveFileEx(f1, f2, MoveFileFlags.MOVEFILE_REPLACE_EXISTING))
                    {
                        var error = GetLastError();
                        Trace.WriteLine("Update MoveFileEx Error:" + error + "\t File:" + oldFiles[i]);
                    }
                    else
                    {
                        list.Add(f2);
                    }
                }
            }
            //Copy New Files
            for (int i = 0; i < newFiles.Length; i++)
            {
                var n1 = newFiles[i];
                var f1 = Path.Combine(updateDir, n1);
                var f2 = Path.Combine(appDir, n1);
                var dir = Path.GetDirectoryName(f2);
                if (!Directory.Exists(dir))
                    Directory.CreateDirectory(dir);
                if (File.Exists(f1))
                {
                    File.Copy(f1, f2);
                }
            }
            //ClearEmptyDir
            ClearEmptyDir(appDir);
            //Delete Old Files
        }

        #endregion 覆盖安装

        #region Win API

        private enum MoveFileFlags
        {
            MOVEFILE_COPY_ALLOWED = 0x02,
            MOVEFILE_CREATE_HARDLINK = 0x10,
            MOVEFILE_DELAY_UNTIL_REBOOT = 0x04,
            MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x20,
            MOVEFILE_REPLACE_EXISTING = 0x01,
            MOVEFILE_WRITE_THROUGH = 0x08,
        }

        [DllImport("Kernel32.dll")]
        private static extern int GetLastError();

        /// <summary>
        /// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexa
        /// </summary>
        /// <param name="lpExistFileName"></param>
        /// <param name="lpNewFileName"></param>
        /// <param name="flags"></param>
        /// <returns></returns>
        [DllImport("Kernel32.dll")]
        private static extern bool MoveFileEx(string lpExistFileName, string lpNewFileName, MoveFileFlags flags);

        #endregion Win API
    }
}